import org.antlr.runtime.tree.*;
import entries.Entry;


/**
 * The TypeCodeChecker type checks whether all types used in a  program are correct.
 * @author Keith Davelaar
 * @version 2012.06.04
 * 
 */
public class TypeChecker {

	/** '+' operator */
	public static String PLUS = "plus";
	/** '-' operator */
	public static String MINUS = "minus";
	/** '/' operator */
	public static String DIV 	= "divided by";
	/** '*' operator */
	public static String MULT 	= "multiplied by";
	/** '==' operator */
	public static String EQ 	= "equals";
	/** '!=' or '<>' operator */
	public static String NE 	= "doesn't equal";
	/** '<' operator */
	public static String LT 	= "is less than";
	/** '<=' operator */
	public static String LE 	= "is smaller than or equal to";
	/** '>' operator */
	public static String GE 	= "is more than";
	/** '>=' operator */
	public static String GT 	= "is larger than or equal to";
	/** '%' operator */
	public static String MOD 	= "modulo";
	/** '&&' operator */
	public static String AND 	= "and";
	/** '||' operator */
	public static String OR 	= "or";
	/** '!' operator */
	public static String NOT 	= "not";
	
	/**
	 * Check if the typing for the Compound expression is correct
	 * @param ce The CompoundExpression tree (used for error propagation)
	 * @param expression The last statement in the expression
	 * @return The type of the Compound expression
	 * @throws CompilerException
	 */
	public static char checkCompound(CommonTree ce, CodeChecker.statement_return stat) throws CompilerException {
		if(stat.type == Entry.NONE){
			throw new CompilerException(ce, "The last statement is not an expression, which isn't allowed.");
		}
		return stat.type;
	}
	
	/**
	 * Check if the typing for the Arithmetic expression is correct
	 * @param ae The Arithmetic expression tree (used for error propagation)
	 * @param name Name of the operation for which the operand are is being checked
	 * @param op The operand that is going to be type-checked
	 * @return The type of the Arithmetic expression
	 * @throws CompilerException
	 */
	public static char checkArithmetic(CommonTree ae, String name, CodeChecker.operand_return op) throws CompilerException {
		char result = Entry.NONE;
		if (op.type == Entry.VOID) {
			throw new CompilerException(op.tree, "One of the operands used is/Operands used are of type VOID, which isn't allowed.");
		}
		if (name.equals(NOT)) {
			if (op.type == Entry.BOOLEAN) {
				result = Entry.BOOLEAN;
			} else {
				throw new CompilerException(ae, "Incorrect use of operation " + name + " on " + op.type);
			}
		} else if (name.equals(PLUS) || name.equals(MINUS)) {
			if (op.type == Entry.INTEGER) {
				result = Entry.INTEGER;
			} else {
				throw new CompilerException(ae, "Incorrect use of operation " + name + " on " + op.type);
			}
		}
		
		return result;
	}
	
	/**
	 * Check if the typing for the Arithmetic expression is correct
	 * @param ex The Arithmetic expression tree (used for error propagation)
	 * @param name Name of the operation for which the operands are is being checked
	 * @param ex1 The left operand, which will be type-checked
	 * @param ex2 The right operand, which will be type-checked
	 * @return The type of the Arithmetic expression
	 * @throws CompilerException
	 */
	public static char checkArithmetic(CommonTree ex, String name, CodeChecker.expression_return ex1, CodeChecker.expression_return ex2) throws CompilerException {
		char result = Entry.NONE;
		if (ex1.type == Entry.VOID || ex2.type == Entry.VOID) {
			throw new CompilerException(ex, "One of the operands/Operands used is/are of type VOID, which isn't allowed.");
		}
		if (name.equals(PLUS) || name.equals(MINUS) || name.equals(DIV) || name.equals(MULT) || name.equals(MOD)) {
			if (ex1.type == Entry.INTEGER && ex2.type == Entry.INTEGER) {
				result = Entry.INTEGER;
			} else {
				throw new CompilerException(ex, "Incompatible types/Incorrect use of operation "+name+" on "+ex1.type+" and "+ex2.type);
			}
		}

		else if (name.equals(GE) || name.equals(GT) || name.equals(LE) || name.equals(LT)) {
			if (ex1.type == Entry.INTEGER && ex2.type == Entry.INTEGER) {
				result = Entry.BOOLEAN;
			} else { 
				throw new CompilerException(ex, "Incompatible types/Incorrect use of operation "+name+" on "+ex1.type+" and "+ex2.type);
			}
		}

		else if (name.equals(EQ) || name.equals(NE)) {
			if (ex1.type == ex2.type) {
				result = Entry.BOOLEAN;
			} else {
				throw new CompilerException(ex,"Incompatible types "+ex1.type+" and "+ex2.type);
			}
		}

		else if (name.equals(OR) || name.equals(AND)) {
			if (ex1.type == Entry.BOOLEAN && ex2.type == Entry.BOOLEAN) {
				result = Entry.BOOLEAN;
			} else {
				throw new CompilerException(ex, "Incompatible types/Incorrect use of operation "+name+" on "+ex1.type+" and "+ex2.type);
			}
		}
		
		if(result == Entry.NONE){
			throw new CompilerException(ex, "Something went wrong");
		}

		return result;
	}

	/**
	 * Check if the typing for the Assignment expression is correct
	 * @param ae The Assignment expression tree (used for error propagation)
	 * @param type The type of the Assignment expression
	 * @param ex The expression being type-checked
	 * @throws CompilerException
	 */
	public static void checkAssignment(CommonTree ae, char type, CodeChecker.expression_return ex) throws CompilerException {
		if(ex.type == Entry.VOID){
			throw new CompilerException(ae, "Operand used is of type VOID, which isn't allowed.");
		}
		if (type != ex.type) {
			throw new CompilerException(ae, "The types of the assignments don't correspond: "+type+" and "+ex.type);
		}
	}
	
	/**
	 * Check if the typing for the Conditional expression is correct
	 * @param ie The Conditional expression tree (used for error propagation)
	 * @param ce The expression being type-checked
	 * @param ce The (compound) subexpression being type-checked, the IF THEN part of the Conditional expression
	 * @param ce2 The (compound) subexpression being type-checked, the ELSE part of the Conditional expression
	 * @return The type of the Conditional expression
	 * @throws CompilerException
	 */
	public static char checkConditional(CommonTree ie, CodeChecker.compound_return ce, CodeChecker.compound_return ce2, CodeChecker.compound_return ce3) throws CompilerException {
		char result;
		if (ce.type != Entry.BOOLEAN) {
			throw new CompilerException(ie, "Expression is not of type boolean, but of type "+ce.type+", which is not allowed.");
		}

		if (ie.getChildCount() == 3 && ce2.type == ce3.type) {
			result = ce2.type;
		} else {
			result = Entry.VOID;
		}
		return result;
	}
	
	/**
	 * Check if the typing for the Dowhile expression is correct
	 * @param we The Dowhile expression tree (used for error propagation)
	 * @param ce The expression being type-checked
	 * @throws CompilerException
	 */
	public static void checkDowhile(CommonTree we, CodeChecker.compound_return ce) throws CompilerException {
		if (ce.type != Entry.BOOLEAN) {
			throw new CompilerException(we, "The expression used doesn't yield a boolean.");
		}
	}
}